package com.javandad.app.client.searchComponent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.cellview.client.CellList;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import com.javandad.app.client.CodesvrParam;
import com.javandad.app.client.cells.SearchResultCell;
import com.javandad.app.shared.entity.project.*;
import com.javandad.app.client.searchComponent.Search.InvalidArgumentException;
import com.javandad.app.shared.entity.User;
import com.javandad.app.shared.entity.UserGroup;
import com.javandad.app.shared.entity.search.SearchProperties;
import com.javandad.app.shared.entity.search.SearchResult;

public class SearchEngine implements EntryPoint {

	// service
	private final SearchServiceAsync searchService = GWT.create(SearchService.class);

	// searches
	private final List<Search> searches = new ArrayList<Search>();
	
	// *** GUI ***
	private final TextBox freetextSearchTerm = new TextBox();
	private final ListBox searchType = new ListBox(false);
	private final Map<String, Search> searchTypes = new HashMap<String, Search>();
	private ChangeHandler searchTypeChanged;
	private final Button searchButton = new Button("Search");
	private final Map<String, DisclosurePanel> optsPanels = new HashMap<String, DisclosurePanel>();
	private final Label noResults = new Label("no results");
	private final CellList<SearchResult> searchResults = new CellList<SearchResult>(new SearchResultCell());

	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad() {
		RootPanel searchTermContainer = RootPanel.get("searchTermContainer");
		RootPanel searchButtonContainer = RootPanel.get("searchButtonContainer");
		RootPanel searchResultsContainer = RootPanel.get("searchResultsContainer");
		// init engine's gui
		initGUI(searchTermContainer, searchButtonContainer, searchResultsContainer);
		
		// init searches
		initSearches();
		// advanced options panels
		RootPanel advancedSearchOptionsContainer = RootPanel.get("advancedSearchOptionsContainer");
		for(Search search : searches)
		{
			Widget opts = search.getAdvancedOptions();
			if(opts != null)
			{
				DisclosurePanel optsPanel = new DisclosurePanel("advanced");
				optsPanel.setAnimationEnabled(true);
				optsPanel.setContent(opts);
				optsPanel.setVisible(false);
				optsPanels.put(search.getSearchName(), optsPanel);
				advancedSearchOptionsContainer.add(optsPanel);
			}
		}
		// search type selector
		for(Search search : searches)
		{
			String name = search.getSearchName();
			searchTypes.put(name, search);
			searchType.addItem(name);
		}
		searchType.setSelectedIndex(0);
		searchTypeChanged.onChange(null);
	}

	private void initSearches() {
		searches.add(new UserSearch(freetextSearchTerm));
		searches.add(new ProjectSearch(freetextSearchTerm));
		searches.add(new GroupSearch(freetextSearchTerm));
		searches.add(new EverythingSearch(freetextSearchTerm));
	}

	private void initGUI(Panel searchTermContainer, Panel searchButtonContainer, Panel searchResultsContainer) {

		// search gui
		freetextSearchTerm.setText("jondoe");
		searchTypeChanged = new ChangeHandler() {
			public void onChange(ChangeEvent event) {
				// make the selected search's options panel visible
				String searchName = searchType.getValue(searchType.getSelectedIndex());
				for(DisclosurePanel optsPanel : optsPanels.values())
					optsPanel.setVisible(false);
				DisclosurePanel optsPanel = optsPanels.get(searchName);
				if(optsPanel != null)
					optsPanel.setVisible(true);
			}
		};
		searchType.addChangeHandler(searchTypeChanged);
		searchButton.addStyleName("sendButton");

		searchTermContainer.add(freetextSearchTerm);
		HorizontalPanel searchButtonPanel = new HorizontalPanel();
		searchButtonPanel.setBorderWidth(0);
		searchButtonPanel.add(searchType);
		searchButtonPanel.add(searchButton);
		searchButtonContainer.add(searchButtonPanel);

		// Add a handler to send the query to the server
		class SearchHandler implements KeyUpHandler, ClickHandler {
			public void onClick(ClickEvent event) {
				handleSearch();
			}
			public void onKeyUp(KeyUpEvent event) {
				if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
					handleSearch();
				}
			}
		}
		SearchHandler searchHandler = new SearchHandler();
		searchButton.addClickHandler(searchHandler);
		freetextSearchTerm.addKeyUpHandler(searchHandler);

		// search result widgets
		searchResults.setVisible(true);
		final SingleSelectionModel<SearchResult> selectionModel = new SingleSelectionModel<SearchResult>();
		searchResults.setSelectionModel(selectionModel);
		selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
			public void onSelectionChange(SelectionChangeEvent event) {
				SearchResult entry = (SearchResult)selectionModel.getSelectedObject(); 
				//Window.alert("SearchResult: " + entry.getType() + " "+entry);
				String codesvrParam = CodesvrParam.getCodesvrParam();
				switch(entry.getSearchResultType())
				{
				case User:
					User user = (User) entry;
					Window.open("home.jsp?"+codesvrParam+"&view="+user.getUsername(), "_self", "");
					break;
				case Group:
					UserGroup group = (UserGroup) entry;
					Window.open("group.jsp?"+codesvrParam+"&group="+group.getGroupURL(), "_self", "");
					break;
				
			case Project:
				Project project = (Project) entry;
				
				Window.open("project.jsp?"+codesvrParam+"&name="+project.getName() , "_self", "");
				break;
			}
			}
		});
		
		noResults.addStyleName("serverResponseLabelError");
		noResults.setVisible(false);
		searchResultsContainer.add(noResults);
		searchResultsContainer.add(searchResults);

		// Focus the cursor on the freetext field when the app loads
		freetextSearchTerm.setFocus(true);
		freetextSearchTerm.selectAll();
	}

	private void handleSearch() {
		Search preparator = searchTypes.get(searchType.getValue(searchType.getSelectedIndex()));
		SearchProperties sp;
		try {
			sp = preparator.getSearchProperties();
			sendSearchToServer(sp);
		} catch (InvalidArgumentException e) {
			Window.alert(e.getMessage());
		}
	}
	
	private void sendSearchToServer(final SearchProperties sp) {
		searchButton.setEnabled(false);
		searchService.search(sp, new AsyncCallback<List<SearchResult>>() {
			public void onFailure(Throwable caught) {
				// Show the RPC error message to the user
				noResults.setText(caught+": \n" + caught.getMessage());
				searchResults.setVisible(true);
				noResults.setVisible(true);
				searchButton.setEnabled(true);
			}

			public void onSuccess(List<SearchResult> results) {
				if (results.isEmpty()) {
					noResults.setText("no results for " + sp + ".");
					searchResults.setVisible(false);
					noResults.setVisible(true);
				} else {
					noResults.setVisible(false);
					searchResults.setVisible(true);
					searchResults.setRowData(results);
				}
				searchButton.setEnabled(true);
			}
		});
	}
}
